﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


namespace System.Windows.Forms.PropertyGridInternal
{
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.ComponentModel;
    using System.Diagnostics;
    using System;
    using System.IO;
    using System.Collections;
    using System.Globalization;
    using System.Reflection;
    using System.ComponentModel.Design;
    using System.ComponentModel.Design.Serialization;
    using System.Windows.Forms;
    using System.Drawing;
    using Microsoft.Win32;

    internal class MergePropertyDescriptor : PropertyDescriptor
    {

        private readonly PropertyDescriptor[] descriptors;

        private enum TriState
        {
            Unknown,
            Yes,
            No
        }

        private TriState localizable = TriState.Unknown;
        private TriState readOnly = TriState.Unknown;
        private TriState canReset = TriState.Unknown;

        private MultiMergeCollection collection;


        public MergePropertyDescriptor(PropertyDescriptor[] descriptors) : base(descriptors[0].Name, null)
        {
            this.descriptors = descriptors;
        }


        /// <summary>
        ///    <para>
        ///       When overridden in a derived class, gets the type of the
        ///       component this property
        ///       is bound to.
        ///    </para>
        /// </summary>
        public override Type ComponentType
        {
            get
            {
                return descriptors[0].ComponentType;
            }
        }

        /// <summary>
        ///    <para>
        ///       Gets the type converter for this property.
        ///    </para>
        /// </summary>
        public override TypeConverter Converter
        {
            get
            {
                return descriptors[0].Converter;
            }
        }

        public override string DisplayName
        {
            get
            {
                return descriptors[0].DisplayName;
            }
        }

        /// <summary>
        ///    <para>
        ///       Gets a value
        ///       indicating whether this property should be localized, as
        ///       specified in the <see cref='System.ComponentModel.LocalizableAttribute'/>.
        ///    </para>
        /// </summary>
        public override bool IsLocalizable
        {
            get
            {
                if (localizable == TriState.Unknown)
                {
                    localizable = TriState.Yes;
                    foreach (PropertyDescriptor pd in descriptors)
                    {
                        if (!pd.IsLocalizable)
                        {
                            localizable = TriState.No;
                            break;
                        }
                    }
                }
                return (localizable == TriState.Yes);
            }
        }

        /// <summary>
        ///    <para>
        ///       When overridden in
        ///       a derived class, gets a value
        ///       indicating whether this property is read-only.
        ///    </para>
        /// </summary>
        public override bool IsReadOnly
        {
            get
            {
                if (readOnly == TriState.Unknown)
                {
                    readOnly = TriState.No;
                    foreach (PropertyDescriptor pd in descriptors)
                    {
                        if (pd.IsReadOnly)
                        {
                            readOnly = TriState.Yes;
                            break;
                        }
                    }
                }
                return (readOnly == TriState.Yes);
            }
        }


        /// <summary>
        ///    <para>
        ///       When overridden in a derived class,
        ///       gets the type of the property.
        ///    </para>
        /// </summary>
        public override Type PropertyType
        {
            get
            {
                return descriptors[0].PropertyType;
            }
        }

        public PropertyDescriptor this[int index]
        {
            get
            {
                return descriptors[index];
            }
        }

        /// <summary>
        ///    <para>
        ///       When overridden in a derived class, indicates whether
        ///       resetting the <paramref name="component "/>will change the value of the
        ///    <paramref name="component"/>.
        /// </para>
        /// </summary>
        public override bool CanResetValue(object component)
        {
            Debug.Assert(component is Array, "MergePropertyDescriptor::CanResetValue called with non-array value");
            if (canReset == TriState.Unknown)
            {
                canReset = TriState.Yes;
                Array a = (Array)component;
                for (int i = 0; i < descriptors.Length; i++)
                {
                    if (!descriptors[i].CanResetValue(GetPropertyOwnerForComponent(a, i)))
                    {
                        canReset = TriState.No;
                        break;
                    }
                }

            }
            return (canReset == TriState.Yes);
        }

        /// <summary>
        ///     This method attempts to copy the given value so unique values are
        ///     always passed to each object.  If the object cannot be copied it
        ///     will be returned.
        /// </summary>
        private object CopyValue(object value)
        {
            // null is always OK
            if (value == null)
            {
                return value;
            }

            Type type = value.GetType();

            // value types are always copies
            if (type.IsValueType)
            {
                return value;
            }

            object clonedValue = null;

            // ICloneable is the next easiest thing
            if (value is ICloneable clone)
            {
                clonedValue = clone.Clone();
            }

            // Next, access the type converter
            if (clonedValue == null)
            {
                TypeConverter converter = TypeDescriptor.GetConverter(value);
                if (converter.CanConvertTo(typeof(InstanceDescriptor)))
                {
                    // Instance descriptors provide full fidelity unless
                    // they are marked as incomplete.
                    InstanceDescriptor desc = (InstanceDescriptor)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(InstanceDescriptor));
                    if (desc != null && desc.IsComplete)
                    {
                        clonedValue = desc.Invoke();
                    }
                }

                // If that didn't work, try conversion to/from string
                if (clonedValue == null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))
                {
                    object stringRep = converter.ConvertToInvariantString(value);
                    clonedValue = converter.ConvertFromInvariantString((string)stringRep);
                }
            }



            // How about serialization?
            if (clonedValue == null && type.IsSerializable)
            {
                BinaryFormatter f = new BinaryFormatter();
                MemoryStream ms = new MemoryStream();
                f.Serialize(ms, value);
                ms.Position = 0;
                clonedValue = f.Deserialize(ms);
            }

            if (clonedValue != null)
            {
                return clonedValue;
            }

            // we failed.  This object's reference will be set on each property.
            return value;
        }

        /// <summary>
        ///    <para>
        ///       Creates a collection of attributes using the
        ///       array of attributes that you passed to the constructor.
        ///    </para>
        /// </summary>
        protected override AttributeCollection CreateAttributeCollection()
        {
            return new MergedAttributeCollection(this);
        }


        private object GetPropertyOwnerForComponent(Array a, int i)
        {
            object propertyOwner = a.GetValue(i);
            if (propertyOwner is ICustomTypeDescriptor)
            {
                propertyOwner = ((ICustomTypeDescriptor)propertyOwner).GetPropertyOwner(descriptors[i]);
            }
            return propertyOwner;
        }

        /// <summary>
        ///    <para>
        ///       Gets an editor of the specified type.
        ///    </para>
        /// </summary>
        public override object GetEditor(Type editorBaseType)
        {
            return descriptors[0].GetEditor(editorBaseType);
        }


        /// <summary>
        ///    <para>
        ///       When overridden in a derived class, gets the current
        ///       value
        ///       of the
        ///       property on a component.
        ///    </para>
        /// </summary>
        public override object GetValue(object component)
        {
            Debug.Assert(component is Array, "MergePropertyDescriptor::GetValue called with non-array value");
            return GetValue((Array)component, out bool temp);
        }

        public object GetValue(Array components, out bool allEqual)
        {
            allEqual = true;
            object obj = descriptors[0].GetValue(GetPropertyOwnerForComponent(components, 0));

            if (obj is ICollection)
            {
                if (collection == null)
                {
                    collection = new MultiMergeCollection((ICollection)obj);
                }
                else if (collection.Locked)
                {
                    return collection;
                }
                else
                {
                    collection.SetItems((ICollection)obj);
                }
            }

            for (int i = 1; i < descriptors.Length; i++)
            {
                object objCur = descriptors[i].GetValue(GetPropertyOwnerForComponent(components, i));

                if (collection != null)
                {
                    if (!collection.MergeCollection((ICollection)objCur))
                    {
                        allEqual = false;
                        return null;
                    }
                }
                else if ((obj == null && objCur == null) ||
                         (obj != null && obj.Equals(objCur)))
                {

                    continue;
                }
                else
                {
                    allEqual = false;
                    return null;
                }
            }

            if (allEqual && collection != null && collection.Count == 0)
            {
                return null;
            }

            return (collection ?? obj);
        }

        internal object[] GetValues(Array components)
        {
            object[] values = new object[components.Length];

            for (int i = 0; i < components.Length; i++)
            {
                values[i] = descriptors[i].GetValue(GetPropertyOwnerForComponent(components, i));
            }
            return values;
        }

        /// <summary>
        ///    <para>
        ///       When overridden in a derived class, resets the
        ///       value
        ///       for this property
        ///       of the component.
        ///    </para>
        /// </summary>
        public override void ResetValue(object component)
        {

            Debug.Assert(component is Array, "MergePropertyDescriptor::ResetValue called with non-array value");
            Array a = (Array)component;
            for (int i = 0; i < descriptors.Length; i++)
            {
                descriptors[i].ResetValue(GetPropertyOwnerForComponent(a, i));
            }
        }

        private void SetCollectionValues(Array a, IList listValue)
        {

            try
            {
                if (collection != null)
                {
                    collection.Locked = true;
                }

                // now we have to copy the value into each property.
                object[] values = new object[listValue.Count];

                listValue.CopyTo(values, 0);

                for (int i = 0; i < descriptors.Length; i++)
                {
                    if (!(descriptors[i].GetValue(GetPropertyOwnerForComponent(a, i)) is IList propList))
                    {
                        continue;
                    }

                    propList.Clear();
                    foreach (object val in values)
                    {
                        propList.Add(val);
                    }
                }
            }
            finally
            {
                if (collection != null)
                {
                    collection.Locked = false;
                }
            }

        }

        /// <summary>
        ///    <para>
        ///       When overridden in a derived class, sets the value of
        ///       the component to a different value.
        ///    </para>
        /// </summary>
        public override void SetValue(object component, object value)
        {
            Debug.Assert(component is Array, "MergePropertyDescriptor::SetValue called with non-array value");
            Array a = (Array)component;
            if (value is IList && typeof(IList).IsAssignableFrom(PropertyType))
            {
                SetCollectionValues(a, (IList)value);
            }
            else
            {
                for (int i = 0; i < descriptors.Length; i++)
                {
                    object clonedValue = CopyValue(value);
                    descriptors[i].SetValue(GetPropertyOwnerForComponent(a, i), clonedValue);
                }
            }
        }

        /// <summary>
        ///    <para>
        ///       When overridden in a derived class, indicates whether the
        ///       value of
        ///       this property needs to be persisted.
        ///    </para>
        /// </summary>

        public override bool ShouldSerializeValue(object component)
        {
            Debug.Assert(component is Array, "MergePropertyDescriptor::ShouldSerializeValue called with non-array value");
            Array a = (Array)component;
            for (int i = 0; i < descriptors.Length; i++)
            {
                if (!descriptors[i].ShouldSerializeValue(GetPropertyOwnerForComponent(a, i)))
                {
                    return false;
                }
            }
            return true;
        }

        private class MultiMergeCollection : ICollection
        {

            private object[] items;
            private bool locked;

            public MultiMergeCollection(ICollection original)
            {
                SetItems(original);
            }

            /// <summary>
            ///     Retrieves the number of items.
            /// </summary>
            public int Count
            {
                get
                {
                    if (items != null)
                    {
                        return items.Length;
                    }
                    else
                    {
                        return 0;
                    }
                }
            }


            /// <summary>
            ///     Prevents the contents of the collection from being re-initialized;
            /// </summary>
            public bool Locked
            {
                get
                {
                    return locked;
                }
                set
                {
                    locked = value;
                }
            }

            object ICollection.SyncRoot
            {
                get
                {
                    return this;
                }
            }

            bool ICollection.IsSynchronized
            {
                get
                {
                    return false;
                }
            }

            public void CopyTo(Array array, int index)
            {
                if (items == null)
                {
                    return;
                }

                Array.Copy(items, 0, array, index, items.Length);
            }

            public IEnumerator GetEnumerator()
            {
                if (items != null)
                {
                    return items.GetEnumerator();
                }
                else
                {
                    return Array.Empty<object>().GetEnumerator();
                }
            }

            /// <summary>
            /// Ensures that the new collection equals the exisitng one.
            /// Otherwise, it wipes out the contents of the new collection.
            /// </summary>
            public bool MergeCollection(ICollection newCollection)
            {

                if (locked)
                {
                    return true;
                }

                if (items.Length != newCollection.Count)
                {
                    items = Array.Empty<object>();
                    return false;
                }

                object[] newItems = new object[newCollection.Count];
                newCollection.CopyTo(newItems, 0);
                for (int i = 0; i < newItems.Length; i++)
                {
                    if (((newItems[i] == null) != (items[i] == null)) ||
                        (items[i] != null && !items[i].Equals(newItems[i])))
                    {
                        items = Array.Empty<object>();
                        return false;
                    }

                }
                return true;
            }

            public void SetItems(ICollection collection)
            {
                if (locked)
                {
                    return;
                }
                items = new object[collection.Count];
                collection.CopyTo(items, 0);
            }

        }

        private class MergedAttributeCollection : AttributeCollection
        {
            private readonly MergePropertyDescriptor owner;

            private AttributeCollection[] attributeCollections = null;
            private IDictionary foundAttributes = null;

            public MergedAttributeCollection(MergePropertyDescriptor owner) : base((Attribute[])null)
            {
                this.owner = owner;
            }

            public override Attribute this[Type attributeType]
            {
                get
                {
                    return GetCommonAttribute(attributeType);
                }
            }

#if false
            private void FullMerge() {
                Attribute[][] collections = new Attribute[owner.descriptors.Length][];
                for (int i = 0; i < owner.descriptors.Length; i++) {
                    AttributeCollection attrCollection = owner.descriptors[i].Attributes;
                    collections[i] = new Attribute[attrCollection.Count];
                    attrCollection.CopyTo(collections[i], 0);
                    Array.Sort(collections[i], GridEntry.AttributeTypeSorter);
                }
                
                ArrayList mergedList = new ArrayList();
    
                // merge the sorted lists -- note that lists aren't fully sorted just by
                // Attribute.TypeId
                //
                int[] posArray = new int[collections.Length];
                for (int i = 0; i < collections[0].Length; i++) {
                    Attribute pivotAttr = collections[0][i];
                    bool match = true;
                    for (int j = 1; j < collections.Length; j++) {
    
                        if (posArray[j] >= collections[j].Length) {
                            match = false;
                            break;
                        }
    
                        // check to see if we're on a match
                        //
                        if (pivotAttr.Equals(collections[j][posArray[j]])) {
                            posArray[j] += 1;
                            continue;
                        }
    
                        int jPos = posArray[j];
                        Attribute jAttr = collections[j][jPos];
    
                        match = false;
    
                        // if we aren't on a match, check all the items until we're past
                        // where the matching item would be
                        while (GridEntry.AttributeTypeSorter.Compare(jAttr, pivotAttr) <= 0) {
                            
                            // got a match!
                            if (pivotAttr.Equals(jAttr)) {
                                posArray[j] = jPos + 1;
                                match = true;
                                break;
                            }
    
                            // try again
                            jPos++;
                            if (jPos < collections[j].Length) {
                                jAttr = collections[j][jPos];
                            }
                            else {
                                break;
                            }
                        }
    
                        // if we got here, there is no match, quit for this guy
                        if (!match) {
                            posArray[j] = jPos;
                            break;
                        }
                    }
    
                    // do we have a match?
                    if (match) {
                        mergedList.Add(pivotAttr);
                    }
                }
    
                // create our merged array
                Attribute[] mergedAttrs = new Attribute[mergedList.Count];
                mergedList.CopyTo(mergedAttrs, 0);
            }

#endif

            private Attribute GetCommonAttribute(Type attributeType)
            {
                if (attributeCollections == null)
                {
                    attributeCollections = new AttributeCollection[owner.descriptors.Length];
                    for (int i = 0; i < owner.descriptors.Length; i++)
                    {
                        attributeCollections[i] = owner.descriptors[i].Attributes;
                    }
                }

                if (attributeCollections.Length == 0)
                {
                    return GetDefaultAttribute(attributeType);
                }

                Attribute value;
                if (foundAttributes != null)
                {
                    value = foundAttributes[attributeType] as Attribute;
                    if (value != null)
                    {
                        return value;
                    }
                }

                value = attributeCollections[0][attributeType];

                if (value == null)
                {
                    return null;
                }

                for (int i = 1; i < attributeCollections.Length; i++)
                {
                    Attribute newValue = attributeCollections[i][attributeType];
                    if (!value.Equals(newValue))
                    {
                        value = GetDefaultAttribute(attributeType);
                        break;
                    }
                }

                if (foundAttributes == null)
                {
                    foundAttributes = new Hashtable();
                }
                foundAttributes[attributeType] = value;
                return value;
            }
        }
    }
}
